package com.ebraga.trackerclub.android.util.webservice;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.core.MediaType;

import org.apache.http.HttpStatus;
import org.apache.http.conn.ssl.SSLSocketFactory;

import com.ebraga.trackerclub.android.exception.BackofficeConnectionException;
import com.ebraga.trackerclub.android.exception.BackofficeIllegalArgumentUtilException;
import com.ebraga.trackerclub.android.exception.BackofficeUtilException;
import com.ebraga.trackerclub.android.util.ISmartServiceConstants;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.WebResource.Builder;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.jersey.client.urlconnection.HTTPSProperties;

/**
 * Provides methods to assist HTTPS calls.
 */
public class HTTPSClientUtil {

	final static String KEY_STORE_TYPE = "PKCS12";
	final static int CONNECT_TIMEOUT = 10000;
	
	/**
	 * Returns a {@link TrustManager} instance using {@link KeyStore} default type, the trust store file content and trust store password.
	 * @param trustStoreInputStream source of trust store file content.
	 * @param trustStorePassword the trust store password.
	 * @return a {@link TrustManager} instance to build HTTPS client config. 
	 * @throws BackofficeUtilException if any error occurs.
	 */
	public static TrustManager[] createTrustManager(InputStream trustStoreInputStream, String trustStorePassword) throws BackofficeUtilException {
		try {
			KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
			keyStore.load(trustStoreInputStream, trustStorePassword.toCharArray());
			
			TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
			tmf.init(keyStore);
			TrustManager[] trustManagers = tmf.getTrustManagers();
			
			return trustManagers;
		} catch (Exception e) {
			throw new BackofficeUtilException("Error while creating TrustManager.", e);
		} finally {
			try {
				trustStoreInputStream.close();
			} catch (IOException e) {
				// ignore
			}
		}
	}
	
	/**
	 * Returns a {@link ClientConfig} instance with HTTPS settings.
	 * @param trustManager parameter used to create HTTPS settings
	 * @return a {@link ClientConfig} instance with HTTPS settings.
	 * @throws BackofficeUtilException if any error occurs.
	 */
	private static ClientConfig getClientConfig(TrustManager[] trustManager) throws BackofficeUtilException {
		SSLContext sslContext;
		try {
			sslContext = SSLContext.getInstance("SSL");
			sslContext.init(null, trustManager, null);
			
			ClientConfig config = new DefaultClientConfig();
			config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new HTTPSProperties(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER, sslContext));
			
			return config;
		} catch (NoSuchAlgorithmException e) {
			throw new BackofficeUtilException("Error while getting Client Config.", e);
		} catch (KeyManagementException e) {
			throw new BackofficeUtilException("Error while getting Client Config.", e);
		}		
	}
	
	/**
	 * Returns a {@link Client} instance for the given parameters.
	 * @param trustStoreInputStream the trust store input stream. 
	 * @param trustStorePassword trust store password.
	 * @param trustStoreInputStream the trust store input stream. 
	 * @param trustStorePassword trust store password. 
	 * @return a {@link Client} instance.
	 * @throws BackofficeUtilException if any error occurs.
	 */
	public static Client getClient(InputStream trustStoreInputStream1, String trustStorePassword1, InputStream trustStoreInputStream2, String trustStorePassword2) throws BackofficeUtilException {
				
		return getClient(trustStoreInputStream1, trustStorePassword1, trustStoreInputStream2, trustStorePassword2, null);
	}
	

    private static TrustManager[] get_trust_mgr() {
          TrustManager[] certs = new TrustManager[] { new X509TrustManager() {
                 public X509Certificate[] getAcceptedIssuers() {
                        return null;
                 }

                 public void checkClientTrusted(X509Certificate[] certs, String t) {
                 }

                 public void checkServerTrusted(X509Certificate[] certs, String t) {
                 }
          } };
          return certs;
    }

	

	private static ClientConfig getClientConfigTmp(InputStream trustStoreInputStream1, String trustStorePassword1, InputStream trustStoreInputStream2, String trustStorePassword2) throws BackofficeUtilException {
		try {
			KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
			keyStore.load(trustStoreInputStream1, trustStorePassword1.toCharArray());
			KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
			keyManagerFactory.init(keyStore, ISmartServiceConstants.WEBSERVICE_CERTIFICATE.KEY_STORE_PASSWORD.toCharArray());			
			
			//TrustManager[] tms = get_trust_mgr();
			
			TrustManager[] tms = null;
			
			if (trustStoreInputStream2 != null) {
				KeyStore trustStore = KeyStore.getInstance("BKS");
				trustStore.load(trustStoreInputStream2, trustStorePassword2.toCharArray());
				TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
				tmf.init(trustStore);
				tms = tmf.getTrustManagers();
			}
			
			SSLContext sslContext;
			sslContext = SSLContext.getInstance("TLS");
			sslContext.init(keyManagerFactory.getKeyManagers(), tms, new SecureRandom());
			
			ClientConfig config = new DefaultClientConfig();
			config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new HTTPSProperties(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER, sslContext));
			
			return config;			
			
		} catch (Exception e) {
			throw new BackofficeUtilException("Error while creating TrustManager.", e);
		} finally {
			try {
				//trustStoreInputStream1.close();
				//trustStoreInputStream2.close();
			} catch (Exception e) {
				// ignore
			}
		}
		
	}
	
	/**
	 * 
	 * Returns a {@link Client} instance for the given parameters.
	 * @param trustStoreInputStream the trust store input stream. 
	 * @param trustStorePassword trust store password.
	 * @param HTTPBasicAuthFilter
	 * @return a {@link Client} instance.
	 * @throws BackofficeUtilException if any error occurs.
	 */
	
	/**
	 * Returns a {@link Client} instance for the given parameters.
	 * @param trustStoreInputStream1 the trust store input stream. 
	 * @param trustStorePassword1 trust store password.
	 * @param trustStoreInputStream2 the trust store input stream. 
	 * @param trustStorePassword2 trust store password.
	 * @param httpBasicAuthFilter
	 * @return a {@link Client} instance.
	 * @throws BackofficeUtilException if any error occurs.
	 */
	
	public static Client getClient(InputStream trustStoreInputStream1, String trustStorePassword1, 
									InputStream trustStoreInputStream2, String trustStorePassword2, 
									HTTPBasicAuthFilter httpBasicAuthFilter) throws BackofficeUtilException {
		
		Client client;
		if(trustStoreInputStream1 != null && trustStorePassword1 != null) {
			
			//TrustManager[] trustManager = createTrustManager(trustStoreInputStream, trustStorePassword);
			//ClientConfig config = getClientConfig(trustManager);			
			
			ClientConfig config = getClientConfigTmp(trustStoreInputStream1, trustStorePassword1, trustStoreInputStream2, trustStorePassword2);
			client = Client.create(config);
		} else {
			client = Client.create();
		}
		
		if (httpBasicAuthFilter != null) {
			client.addFilter(httpBasicAuthFilter);
		}
		
		client.setConnectTimeout(CONNECT_TIMEOUT);
		
		return client;
	}	

	/**
	 * Returns the contents of the POST done through the Client offered for the given URL sending the postContent as JSON if the response status is {@link HttpStatus.SC_CREATED}.
	 * @param client Client offered to make POST call.
	 * @param url the target URL.
	 * @param postJSON the content to be posted.
	 * @return the contents of the POST done through the Client offered for the given URL as JSON.
	 * @throws BackofficeConnectionException if a connection error occurs.
	 * @throws BackofficeUtilException if any error occurs.
	 */
	public static String doPost(Client client, String url, String postJSON) throws BackofficeUtilException, BackofficeConnectionException {
		try {
			if(url == null){
				throw new BackofficeUtilException("Url cannot be null");
			}
			
			WebResource webResource = client.resource(url);
			Builder builder = webResource.type(MediaType.APPLICATION_JSON_TYPE).accept(MediaType.APPLICATION_JSON_TYPE);
			
			if(postJSON == null)
				postJSON = "";
			
			ClientResponse response = builder.post(ClientResponse.class, postJSON);
			if (response.getStatus() != HttpStatus.SC_CREATED && response.getStatus() != HttpStatus.SC_OK) {
				throw new BackofficeUtilException("Failed : HTTP error code : " + response.getStatus());
			}
	 
			return response.getEntity(String.class);
			
		} catch (ClientHandlerException e) {
			throw new BackofficeConnectionException("Error while connecting to: "+ url, e);
		} catch (IllegalArgumentException e) {
			throw new BackofficeIllegalArgumentUtilException(e);
		} catch (Exception e) {
			throw new BackofficeUtilException(e);
		}
	}
	
	/**
	 * Returns the contents of the GET done through the Client offered for the given URL if the response status is {@link HttpStatus.SC_OK}.
	 * @param client Client offered to make GET call.
	 * @param url the target URL.
	 * @return the contents of the GET done through the Client offered for the given URL as JSON.
	 * @throws BackofficeConnectionException if a connection error occurs.
	 * @throws BackofficeUtilException if any error occurs.
	 */
	public static String doGet(Client client, String url) throws BackofficeUtilException, BackofficeConnectionException {
		try {
			if(url == null){
				throw new BackofficeUtilException("Url cannot be null");
			}
			
			WebResource webResource = client.resource(url);
			ClientResponse response = webResource.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);
			if (response.getStatus() != HttpStatus.SC_OK) {
				throw new BackofficeUtilException("Failed : HTTP error code : " + response.getStatus());
			}
			
			return response.getEntity(String.class);
			
		} catch (ClientHandlerException e) {
			throw new BackofficeConnectionException("Error while connecting to: "+ url, e);
		} catch (IllegalArgumentException e) {
			throw new BackofficeIllegalArgumentUtilException(e);
		}
 	}
	

	/**
	 * Returns the contents of the Put done through the Client offered for the given URL sending the putContent as JSON if the response status is {@link HttpStatus.SC_CREATED}.
	 * @param client Client offered to make PUT call.
	 * @param url the target URL.
	 * @param putJSON the content to be posted.
	 * @return the contents of the PUT done through the Client offered for the given URL as JSON.
	 * @throws BackofficeConnectionException if a connection error occurs.
	 * @throws BackofficeUtilException if any error occurs.
	 */
	public static String doPut(Client client, String url, String putJSON) throws BackofficeUtilException, BackofficeConnectionException {
		try {
			if(url == null){
				throw new BackofficeUtilException("Url cannot be null");
			}
			
			WebResource webResource = client.resource(url);
			Builder builder = webResource.type(MediaType.APPLICATION_JSON_TYPE).accept(MediaType.APPLICATION_JSON_TYPE);
			
			if(putJSON == null)
				putJSON = "";
			
			ClientResponse response = builder.put(ClientResponse.class, putJSON);
			if (response.getStatus() != HttpStatus.SC_CREATED && response.getStatus() != HttpStatus.SC_OK) {
				throw new BackofficeUtilException("Failed : HTTP error code : " + response.getStatus());
			}
	 
			return response.getEntity(String.class);
		} catch (ClientHandlerException e) {
			throw new BackofficeConnectionException("Error while connecting to: "+ url, e);
		} catch (IllegalArgumentException e) {
			throw new BackofficeIllegalArgumentUtilException(e);
		}
	}	
}
